package cordova.plugin.zmq.service;

import android.content.Context;
import android.util.Log;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.Iterator;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * socket 处理类
 *
 * @author mj
 *         created at 2016/5/19
 */
public class Client {

    private final int STATE_OPEN = 1;//socket打开
    private final int STATE_CLOSE = 1 << 1;//socket关闭
    private final int STATE_CONNECT_START = 1 << 2;//开始连接server
    private final int STATE_CONNECT_SUCCESS = 1 << 3;//连接成功
    private final int STATE_CONNECT_FAILED = 1 << 4;//连接失败
    private final int STATE_CONNECT_WAIT = 1 << 5;//等待连接
    private static final long Timeout = 150000;  //接收心跳超时时间

    private String IP = "139.196.56.252";
    private int PORT = 2023;

    private int state = STATE_CONNECT_START;

    private Socket socket = null;
    private OutputStream outStream = null;
    private InputStream inStream = null;

    private Thread conn = null;  //链接线程
    private Thread send = null;  //发送线程
    private Thread rec = null;   //接收线程

    private Context context;
    private ISocketResponse respListener;
    private LinkedBlockingQueue<Packet> requestQueen = new LinkedBlockingQueue<Packet>();  //发送信息队列
    private final Object lock = new Object();
    private final String TAG = "Client";
    private long lasttime = 0;  //记录接收到心跳时间

    public int send(Packet in) {
        requestQueen.add(in);
        synchronized (lock) {
            lock.notifyAll();
        }
        return in.getId();
    }

    public void cancel(int reqId) {
        Iterator<Packet> mIterator = requestQueen.iterator();
        while (mIterator.hasNext()) {
            Packet packet = mIterator.next();
            if (packet.getId() == reqId) {
                mIterator.remove();
            }
        }
    }

//    public Client(Context context, ISocketResponse respListener) {
//        this.context = context;
//        this.respListener = respListener;
//    }

    private static Client instance = new Client();

    private Client() {
    }

    public static Client getInstance() {
        return instance;
    }

    public void setRespListener(ISocketResponse respListener) {
        this.respListener = respListener;
    }

    public void setContext(Context context) {
        this.context = context;
    }


    public boolean isNeedConn() {
        return !((state == STATE_CONNECT_SUCCESS) && (null != send && send.isAlive()) && (null != rec && rec.isAlive()));
    }

    public void open() {
        reconn();
    }

    /**
     * 打开链接
     *
     * @param host 链接地址
     * @param port 端口
     */
    public void open(String host, int port) {
        this.IP = host;
        this.PORT = port;
        reconn();
    }

    private long lastConnTime = 0;

    public synchronized void reconn() {
        if (System.currentTimeMillis() - lastConnTime < 2000) {
            return;
        }
        lastConnTime = System.currentTimeMillis();

        close();
        state = STATE_OPEN;
        if (conn == null) {
            conn = new Thread(new Conn());
            conn.start();
        }
    }

    /**
     * 断开链接
     */
    public synchronized void close() {
        try {
            if (state != STATE_CLOSE) {

                try {
                    if (null != conn && conn.isAlive()) {
                        conn.interrupt();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    conn = null;
                }

                try {
                    if (null != send && send.isAlive()) {
                        send.interrupt();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    send = null;
                }

                try {
                    if (null != rec && rec.isAlive()) {
                        rec.interrupt();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    rec = null;
                }

                try {
                    if (null != socket) {
                        socket.shutdownInput();
                        socket.shutdownOutput();
                        socket.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    socket = null;
                }

                try {
                    if (null != outStream) {
                        outStream.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    outStream = null;
                }

                try {
                    if (null != inStream) {
                        inStream.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    inStream = null;
                }

                state = STATE_CLOSE;
            }
            respListener.onCallBackStatus(3, state);
            requestQueen.clear();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private class Conn implements Runnable {
        public void run() {
            Log.v(TAG, "Conn :Start");
            try {
                while (state != STATE_CONNECT_SUCCESS) {
                    try {
                        state = STATE_CONNECT_START;
                        socket = new Socket();
                        socket.connect(new InetSocketAddress(IP, PORT), 15 * 1000);
                        socket.setSoTimeout(1000);
                        socket.setSoLinger(true, 0);
                        state = STATE_CONNECT_SUCCESS;
                    } catch (Exception e) {
                        e.printStackTrace();
                        state = STATE_CONNECT_FAILED;
                    }
                    respListener.onCallBackStatus(1, state);

                    if (state == STATE_CONNECT_SUCCESS) {
                        try {
                            outStream = socket.getOutputStream();
                            inStream = socket.getInputStream();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                        send = new Thread(new Send());
                        rec = new Thread(new Rec());
                        send.start();
                        rec.start();
                        break;
                    } else {
                        state = STATE_CONNECT_WAIT;
                        //如果有网络没有连接上，则定时取连接，没有网络则直接退出
                        if (NetworkUtil.isNetworkAvailable(context)) {
                            try {
                                Thread.sleep(15 * 1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                                break;
                            }
                        }
                    }

                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            Log.v(TAG, "Conn :End");
        }
    }

    /**
     * 发送消息
     */
    private class Send implements Runnable {
        public void run() {
            Log.v(TAG, "Send :Start");
            try {
                while (state != STATE_CLOSE && state == STATE_CONNECT_SUCCESS && null != outStream) {
                    Packet item;
                    while (null != (item = requestQueen.poll())) {
                        outStream.write(item.getPacket());
                        outStream.flush();
                        respListener.onCallBackStatus(2, 1);
                        item = null;
                    }

                    Log.v(TAG, "Send :woken up AAAAAAAAA");
                    synchronized (lock) {
                        lock.wait();
                    }
                    Log.v(TAG, "Send :woken up BBBBBBBBBB");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (SocketException e1) {
                e1.printStackTrace();//发送的时候出现异常，说明socket被关闭了(服务器关闭)java.net.SocketException: sendto failed: EPIPE (Broken pipe)
                respListener.onCallBackStatus(2, 0);
                reconn();
            } catch (Exception e) {
                respListener.onCallBackStatus(2, 0);
                Log.v(TAG, "Send ::Exception");
                e.printStackTrace();
            }

            Log.v(TAG, "Send ::End");
        }
    }

    /**
     * 接收消息
     */
    private class Rec implements Runnable {
        public void run() {
            Log.v(TAG, "Rec :Start");
            try {
                if (receiveId() == 1) {
                    respListener.onSetEmptyMsg();
                    lasttime = System.currentTimeMillis();
                    String temp = "";
                    while (state != STATE_CLOSE && state == STATE_CONNECT_SUCCESS && null != inStream) {
                        Log.v(TAG, "Rec :---------");
                        try {
                            byte[] cbuf = new byte[1024];
                            int i = inStream.read(cbuf);
                            if (i != -1) {
                                String aa = temp + new String(cbuf);
                                temp = "";
                                char[] info = aa.toCharArray();
                                int k = 0;
                                while (k < info.length) {
                                    if (k == info.length - 1) {
                                        if (info[k] == '\0') {
                                            break;
                                        } else {
                                            temp += info[k];
                                            break;
                                        }
                                    }
                                    if (info[k] == '\r' && info[k + 1] == '\n') {
                                        if (null != respListener) {
                                            String msg = temp;
                                            if (msg.equals("200")) {
                                                lasttime = System.currentTimeMillis();
                                            }
                                            if (!msg.equals(""))
                                                respListener.onSocketResponse(msg.trim());
                                            temp = "";
                                        }
                                        if ((k + 1) == info.length - 1) {
                                            break;
                                        }
                                        k = k + 2;
                                    } else {
                                        if (info[k] != '\0') {
                                            temp += info[k];
                                        }
                                        k = k + 1;
                                    }
                                }
                            }

                        } catch (SocketTimeoutException e) {
                            if (System.currentTimeMillis() - lasttime > Timeout) {
                                lasttime = System.currentTimeMillis();
                                reconn();
                            }
                            continue;
                        }
                    }
                } else {
                    reconn();
                }
            } catch (SocketException e1) {
                e1.printStackTrace();//客户端主动socket.close()会调用这里 java.net.SocketException: Socket closed
                reconn();
            } catch (Exception e2) {
                Log.v(TAG, "Rec :Exception");
                e2.printStackTrace();
            }
            Log.v(TAG, "Rec :End");
        }
    }

    private int receiveId() {
        for (int j = 0; j < 3; j++) {
            if (receiveMsg() == 1) {
                return 1;
            } else {
                if (null != respListener) {
                    respListener.onResendId();
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        return 0;
    }


    /**
     * setId 返回结果
     *
     * @return
     */
    private int receiveMsg() {
        try {
            byte[] cbuf = new byte[1024];
            StringBuffer content = new StringBuffer();
            int i = inStream.read(cbuf);
            if (i != -1) {
                String aa = new String(cbuf);
                for (char c : aa.toCharArray()) {
                    if (c == '\r') {
                        break;
                    }
                    content.append(c);
                }
                String msg = content.toString();
                if (msg.equals("202")) {
                    return 1;
                }
            }
        } catch (SocketTimeoutException e) {
            return 0;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return 0;
    }

}
